/*
 * Copyright 2006-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.martinimix.service.payment;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import net.martinimix.dao.payment.PaymentDao;
import net.martinimix.domain.order.Order;
import net.martinimix.domain.order.OrderHeader;
import net.martinimix.domain.payment.Payment;
import net.martinimix.domain.payment.PaymentMethod;
import net.martinimix.service.payment.handler.PaymentHandler;
import net.martinimix.service.payment.handler.PaymentHandlerNotFoundException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Provides a payment service implementation that delegates
 * actual payment service requests to a set of 
 * payment handlers.  Registered payment handlers form a chain
 * of responsibility to handle payments.
 * 
 * @author Scott Rossillo
 *
 */
public class MartiniMixMultiPaymentService implements MultiPaymentService {
	
	private final static Log log = LogFactory.getLog(MartiniMixMultiPaymentService.class);
	
	/**
	 * The payment data access object for this payment service.
	 */
	private PaymentDao paymentDao;
	
	/**
	 * The payment handlers for this payment service.
	 */
	private List paymentHandlers;
	
	/**
	 * Creates a new multi-payment service.
	 */
	public MartiniMixMultiPaymentService() { }
	

	/* (non-Javadoc)
	 * @see net.martinimix.service.payment.MultiPaymentService#authorizePayments(java.util.List, net.martinimix.domain.order.Order)
	 */
	public void authorizePayments(List paymentMethods, Order order) {
		
		PaymentMethod paymentMethod;
		Payment payment;
		
		if(paymentMethods == null) {
			throw new IllegalArgumentException("Payments cannot be null!");
		}
		
		for(Iterator it = paymentMethods.iterator(); it.hasNext(); ) {
			paymentMethod = (PaymentMethod) it.next();
			payment = authorizePayment(paymentMethod, order);
			order.getPayments().add(payment);
		}	
	}
	
	/* (non-Javadoc)
	 * @see net.martinimix.service.payment.PaymentService#authorizePayment(net.martinimix.domain.payment.PaymentMethod, net.martinimix.domain.order.Order)
	 */
	public final Payment authorizePayment(PaymentMethod paymentMethod, Order order) {
		
		Payment payment = this.findPayment(paymentMethod, order.getPayments());
		PaymentHandler paymentHandler = this.findPaymentHandler(paymentMethod);
		
		
		if(log.isDebugEnabled()) {
			log.debug("Authorizing payment method " + "[" + paymentMethod + "] " +
							"for order [" + order.getOrderHeader().getExternalOrderNumber() + "] " +
							"with payment handler [" + paymentHandler + "]");
		}
		
		if(paymentHandler.authorizationRequired(paymentMethod)) {
			payment = paymentHandler.authorizePayment(paymentMethod, order);	
		}
		
		return payment;
	}


	/**
	 * Creates a new payment for the given payment method.
	 * 
	 * @throws PaymentHandlerNotFoundException if no payment handler could be found
	 * for the given payment method
	 * 
	 * @throws IllegalArgumentException if the given <code>orderHeader</code> or
	 * <code>paymentMethod</code> is <code>null</code>
	 * 
	 * @see #setPaymentHandlers(Set)
	 */
	public final Payment createPayment(PaymentMethod paymentMethod, OrderHeader orderHeader) {
		
		Payment payment = null;
		PaymentHandler paymentHandler;
		
		if(orderHeader == null) {
			throw new IllegalArgumentException("Cannot create a payment without an [OrderHeader]!");
		}
		
		if(paymentMethod == null) {
			throw new IllegalArgumentException("Cannot create a payment without a [PaymentMethod]!");
		}
		
		paymentHandler = findPaymentHandler(paymentMethod);
		payment = paymentHandler.createPayment(paymentMethod, orderHeader);
		payment.setOrderHeaderId(orderHeader.getId());		
		
		paymentDao.savePayment(payment);
		
		return payment;
	}
	
	/* (non-Javadoc)
	 * @see net.martinimix.service.payment.MultiPaymentService#createPayments(java.util.List, net.martinimix.domain.order.OrderHeader)
	 */
	public final List createPayments(List paymentMethods, OrderHeader orderHeader) {
		
		final List payments = new ArrayList(paymentMethods.size());
		PaymentMethod paymentMethod;
		
		for(Iterator it = paymentMethods.iterator(); it.hasNext(); ) {
			paymentMethod = (PaymentMethod) it.next();
			payments.add(createPayment(paymentMethod, orderHeader));
		}
		
		return payments;
	}
	
	/**
	 * Returns the order payment for the given payment method.
	 *  
	 * @param paymentMethod the <code>PaymentMethod</code> whose payment
	 * should be returned
	 * 
	 * @param payments a <code>Collection</code> of <code>Payment</code>s
	 * 
	 * @return the order <code>Payment</code> for the given <code>paymentMethod</code>
	 */
	protected Payment findPayment(PaymentMethod paymentMethod, Collection payments) {
		
		Payment payment;
		
		for(Iterator it = payments.iterator(); it.hasNext(); ) {
			
			payment = (Payment) it.next();
			if(payment.getPaymentTypeId().longValue() == paymentMethod.getId().longValue()) {
				return payment;
			}
		}
		
		return null;
	}
	
	/**
	 * Returns the handler for the given payment method.
	 * 
	 * @param paymentMethod the <code>PaymentMethod</code> whose
	 * payment handler should be returned
	 * 
	 * @return the <code>PaymentHandler</code> for the given
	 * <code>paymentMethod</code>
	 * 
	 * @throws PaymentHandlerNotFoundException if no handler has
	 * been registered for the given <code>paymentMethod</code>
	 * 
	 * @see #setPaymentHandlers(Set)
	 */
	public final PaymentHandler findPaymentHandler(PaymentMethod paymentMethod) {
				
		PaymentHandler paymentHandler;
		
		if(paymentHandlers == null) {
			throw new IllegalStateException("No [PaymentHander]s have been set!");
		}
		
		for(Iterator it = paymentHandlers.iterator(); it.hasNext(); ) {
			paymentHandler = (PaymentHandler) it.next();
			if(paymentHandler.supports(paymentMethod)) {
				return paymentHandler;
			}
		}
		
		throw new PaymentHandlerNotFoundException(paymentMethod);
	}
	
	/**
	 * Sets the payment data access object for this service.
	 * 
	 * @param paymentDao the <code>PaymentDao</code> for this <code>PaymentService</code>
	 */
	public void setPaymentDao(PaymentDao paymentDao) {
		this.paymentDao = paymentDao;
	}

	/**
	 * Returns the payment handlers registered with this payment service.
	 * 
	 * @return the <code>List</code> of <code>PaymentHander</code>s registered
	 * with this payment service
	 */
	public List getPaymentHandlers() {
		return Collections.unmodifiableList(paymentHandlers);
	}

	/**
	 * Sets the payment handlers this payment service will to create order
	 * payments.
	 * 
	 * @param paymentHandlers the <code>List</code> of {@link PaymentHander}s
	 * this payment service will use to create order {@link Payment}s; 
	 * may not be <code>null</code>
	 */
	public final void setPaymentHandlers(List paymentHandlers) {
		
		if(paymentHandlers == null) {
			throw new IllegalArgumentException("[PaymentHander] set cannot be null!");
		}
		
		this.paymentHandlers = new ArrayList(paymentHandlers.size());
		this.paymentHandlers.addAll(paymentHandlers);
	}
}
